package cn.neo.hhsharding.mybatis;

import cn.neo.hhsharding.engine.ShardingContext;
import cn.neo.hhsharding.engine.ShardingResult;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import org.springframework.stereotype.Component;
import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.sql.Connection;

/**
 * 拦截sql，拦截mapper文件中方法时触发
 */
@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}))
public class SqlStatementInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        ShardingResult result = ShardingContext.get();
        if (null != result) {
            StatementHandler handler = (StatementHandler) invocation.getTarget();
            BoundSql boundSql = handler.getBoundSql();
            String sql = boundSql.getSql();
            System.out.println(" ===> [SqlStatementInterceptor] 逻辑sql: " + sql);
            // 修改sql，user->user1
            String targetSqlStatement = result.getTargetSqlStatement();
            System.out.println(" ===> [SqlStatementInterceptor] 实际sql: " + targetSqlStatement);

            if (!sql.equalsIgnoreCase(targetSqlStatement)) {
                replaceSql(boundSql, targetSqlStatement);
            }
        }
        return invocation.proceed();
    }

    private static void replaceSql(BoundSql boundSql, String sql) throws NoSuchFieldException {
        // boundSql中的sql是被final修饰的：private final String sql;如何修改sql？
        // 1、通过反射去掉final修饰符“
        // 2、(此处使用此方法)
        Field field = boundSql.getClass().getDeclaredField("sql");
        // 获取Unsafe：直接操作jdk的堆内存/堆外
        Unsafe unsafe = UnsafeUtils.getUnsafe();
        // 指针：内存对齐偏移量
        long fieldOffset = unsafe.objectFieldOffset(field);
        // 通过指针修改内存中的值，避过了jdk中所有的检查操作
        unsafe.putObject(boundSql, fieldOffset, sql);
    }
}
